<?php
class Forum_Mail
{
    /**
     * Mail body
     * @var string
     * @access public
     */
    public $body = '';
    
    /**
     * Mail header string
     * @var string
     * @access public
     */
    public $header = '';
    
    /**
     * Recipients string
     * @var string
     * @access public
     */
    public $recipients = '';
    
    /**
     * Subject: header
     * @var string
     */
    public $subject = null;
    
    /**
     * Mail headers
     * @var array
     */
    private $_headers = array();
    
    /**
     * Array of all recipients
     * @var array
     */
    private $_recipients = array();
    
    /**
     * From: address
     * @var string
     */
    protected $_from = null;
    
    /**
     * EOL character string used by transport
     * @var string
     * @access public
     */
    public $EOL = "\r\n";
    
    /**
     * Send mail using PHP native mail()
     *
     * @access public
     * @return void
     * @throws Exception on mail() failure
     */
    public function send()
    {
        $this->_prepareHeaders($this->_headers);
        $this->recipients = implode(',', array_keys($this->_recipients));
        
        $result = mail(
            $this->recipients,
            $this->subject,
            $this->body,
            $this->header
        );
            
        if (!$result) {
            throw new Exception('Unable to send mail');
        }
    }
    
    /**
     * Prepare header string for use in transport
     *
     * Prepares and generates {@link $header} based on the headers provided.
     *
     * @param mixed $headers
     * @access private
     * @return void
     * @throws Exception if any header lines exceed 998 characters
     */
    private function _prepareHeaders($headers)
    {
        $this->header = '';

        foreach ($headers as $header => $content) {
            $value = implode(',' . $this->EOL . ' ', $content);
            $this->header .= $header . ': ' . $value . $this->EOL;
        }

        // Sanity check on headers -- should not be > 998 characters
        $sane = true;
        foreach (explode($this->EOL, $this->header) as $line) {
            if (strlen(trim($line)) > 998) {
                $sane = false;
                break;
            }
        }
        if (!$sane) {
            throw new Exception('At least one mail header line is too long');
        }
    }
    
    /**
     * Add a header to the message
     *
     * Adds a header to this message. If append is true and the header already
     * exists, raises a flag indicating that the header should be appended.
     *
     * @param string  $headerName
     * @param string  $value
     */
    private function _storeHeader($headerName, $value)
    {
        if (isset($this->_headers[$headerName])) {
            $this->_headers[$headerName][] = $value;
        } else {
            $this->_headers[$headerName] = array($value);
        }
    }
    
    /**
     * Sets From-header and sender of the message
     *
     * @param  string    $email
     * @param  string    $name
     * @return Forum_Mail Provides fluent interface
     * @throws Exception if called subsequent times
     */
    public function setFrom($email, $name = null)
    {
        if ($this->_from === null) {
            $email = $this->_filterEmail($email);
            $name  = $this->_filterName($name);
            $this->_from = $email;
            if ($name !== null && $name !== $email) {
                $from = $name;
            } else {
                $from = $email;
            }
            $this->_storeHeader('From', $from, true);
        } else {
            throw new Exception('From Header set twice');
        }
        return $this;
    }
    
    /**
     * Sets the subject of the message
     *
     * @param   string    $subject
     * @return  Forum_Mail Provides fluent interface
     * @throws  Exception
     */
    public function setSubject($subject)
    {
        if ($this->subject === null) {
            $subject = strtr($subject,"\r\n\t",'???');
            $this->subject = $subject;
            $this->_storeHeader('Subject', $this->subject);
        } else {
            throw new Exception('Subject set twice');
        }
        return $this;
    }
    
    /**
     * Sets the body for the message
     *
     * @param  string    $body
     * @return Forum_Mail Provides fluent interface
     */
    public function setBody($body)
    {
        $this->body = $body;
        return $this;
    }

    /**
     * Add a recipient
     *
     * @param string $email
     * @return Forum_Mail Provides fluent interface
     */
    public function addRecipient($email)
    {
        $this->_recipients[$email] = 1;
        return $this;
    }

    /**
     * Filter of email data
     *
     * @param string $email
     * @return string
     */
    protected function _filterEmail($email)
    {
        $rule = array("\r" => '',
                      "\n" => '',
                      "\t" => '',
                      '"'  => '',
                      ','  => '',
                      '<'  => '',
                      '>'  => '',
        );

        return strtr($email, $rule);
    }

    /**
     * Filter of name data
     *
     * @param string $name
     * @return string
     */
    protected function _filterName($name)
    {
        $rule = array("\r" => '',
                      "\n" => '',
                      "\t" => '',
                      '"'  => "'",
                      '<'  => '[',
                      '>'  => ']',
        );

        return strtr($name, $rule);
    }
}